Obsidian Internal Link를 Next.js에서 작동시키는 방법에 대한 고찰
옵시디언에서 사용하는 마크다운 문법을 변환하려고 보면 제대로 작동하지 않는 몇가지 문법들이 있는데 대표적인것이 바로 Wiki Link ([Wiki Link](Wiki%20Link)
) 그리고 highlight ('==highlight=='
) 이다. highlight의 경우, remark-mark-highlight를 사용하면 간단하게 적용을 할 수 있지만, 위키링크의 경우 플러그인 설정! 짠! 하고 나오는게 아니었고 ... 추가적으로 설정이 필요했는데 그 삽질 과정을 기록해볼까 한다.
1. 옵시디언 플러그인 사용
obsidian-link-converter 라는 옵시디언 플러그인을 사용하여 명시적으로 경로를 설정해주는 방법.
해당 플러그인을 사용하면 세가지 옵션(Relative Path
, Absolute Path
, Shortest Path
) 중 하나로 Wiki Link를 Markdown Link([]()
)형식으로 변환해준다.
[위키링크](위키링크)
Relative Path : [위키링크](위키링크.md)
Absolute Path : [위키링크](.../.../위키링크.md)
나는 Absolute Path로 변환을 해서 문서를 작업해서 올렸고 ...여기서 큰 단점이 발생하고 마는데 ...
저렇게 띨롱 변환해서 넘겨버리면 경로 자체가 href
로 넘어가게 되어버린다. 당연히 웹에서 링크를 클릭을 해도 경로를 찾을 수가 없으니 에러를 뱉는다. 그래서 고안해낸 방법이 Middleware
을 거쳐서 페이지를 이동시키는 것이었다.
import { NextRequest, NextResponse } from "next/server";
// href가 Suffix/카테고리/파일명.md라고 가정
function removeLeadingAndExtension(str: string) {
return str.replace('Suffix/', "").replace(/\.md$/, "");
}
export function middleware(req: NextRequest) {
const url = req.nextUrl.clone();
const pathName = req.nextUrl.pathname;
const pathRegex = "Suffix[^?]*.md$";
if (pathName.endsWith(".md")) {
const [pathValue] = pathName.match(pathRegex) ?? "";
const decodedPath = decodeURIComponent(pathValue);
url.pathname = "/post/" + removeLeadingAndExtension(decodedPath);
return NextResponse.redirect(url);
}
return NextResponse.next();
}
하지만 이 방법에도 단점은 있었는데 ... 소스코드에 파일 경로가 전부 다 노출이 된다.
최종절망
아무리 내 옵시디언 레포가 Private이라지만 전체 경로가 다 보이니 여간 찝찝한게 아니어서 바꿀 결심을 했다.
2. remark-wiki-link 사용
역시 위키링크 변환에 대한 수요가 있었는지 위키링크 형식을 노드로 파싱해서 <a>
로 렌더링을 해주는 플러그인이 이미 존재했다.
해당 플러그인을 사용을 하면 아래와 같은 마크다운 AST의 노드로 변환이 되고
{
value: 'Test Page',
data: {
alias: 'Test Page',
permalink: 'test_page',
exists: false,
hName: 'a',
hProperties: {
className: 'internal new',
href: '#/page/test_page'
},
hChildren: [{
type: 'text',
value: 'Test Page'
}]
}
}
이렇게 html로 렌더링이 된다
<a class="internal new" href="#/page/test_page">Test Page</a>
현재 사용하고 있는 블로그의 경우, 글의 상세페이지 경로는 /post/[글제목]
형식으로 되어있어서 config option도 같이 수정을 해 주었다. 제공하는 옵션들은 아래와 같다.
permalinks [String]
- 존재하는 페이지로 간주할 permalinks를 설정
- 위키링크 파싱 시, permalink가 이 배열 중 하나와 일치하면 해당 노드의
data.exists
속성이true
로 설정됨
pageResolver (pageName: String) => [String]
- 위키링크를 실제 경로나 url로 매핑
//Default
(name) =>[name.replace(/ /g, '_').toLowerCase()]
hrefTemplate (permalink: String) => String
- 파싱할 때 얻은 permalink를 a태그의 href 속성으로 설정할 때 사용
//Default
(permalink) => `#/page/${permalink}`
wikiLinkClassName
- 위키링크의 className을 설정. 기본값은
internal
- 위키링크의 className을 설정. 기본값은
newClassName
- 링크가
options.permalinks
에 없는 경우에 추가되는 className. 기본값은new
- 링크가
aliasDivider
- 위키링크 안에서 alias를 구분할 때 사용하는 구분자.
<MDXRemote
source={content}
options={{
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [
//...remark plugins
[
remarkWikiLink,
{
hrefTemplate: (permalink: string) => permalink,
pageResolver: (text: string) => [text],
},
],
],
rehypePlugins: [
//...rehype plugins
],
},
}}
/>
이런 위키링크를 ([[Obsidian Internal Link를 Next.js에서 작동시키는 방법에 대한 고찰]]
) HTML에 렌더링해서 확인해보면?
<a class="internal-link" href="Obsidian Internal Link를 Next.js에서 작동시키는 방법에 대한 고찰">Obsidian Internal Link를 Next.js에서 작동시키는 방법에 대한 고찰</a>
예상대로 잘 작동한다!
번외 : rehype-rewrite
옵시디언 플러그인에서 remark wiki link로 넘어가기 전에 찍먹했던 플러그인이다. 이 친구로 말할 것 같으면 ... element
를 수정할 수 있게 하는 플러그인인데, 특정 태그에 해당하는 노드를 불러와 속성을 바꾸거나, 요소를 감싸거나 하는 등의 작업을 할 수 있다.
그 말인 즉슨? 앞에서 고민했던 <a>
에 파일 경로가 통으로 넘어가는 문제도 고칠 수 있다는 말이었다.
export declare type RehypeRewriteOptions = {
/**
* Select an element to be wrapped. Expects a string selector that can be passed to hast-util-select ([supported selectors](https://github.com/syntax-tree/hast-util-select/blob/master/readme.md#support)).
* If `selector` is not set then wrap will check for a body all elements.
*/
selector?: string;
/** Rewrite Element. */
rewrite(node: Root | RootContent, index: number | null, parent: Root | Element | null): void;
};
이 옵션을 보면 알 수 있듯이, selector
을 통해서 태그를 불러온 다음에 rewrite
함수로 수정을 하는 식이었는데, 나는 이렇게 적용을 했었다.
<MDXRemote
source={content}
options={{
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [
//...remark plugins
],
rehypePlugins: [
//...rehype plugins
[
rehypeRewrite,
{
selector: "a",
rewrite: (node: Node) => rewriteLinkNodes(node),
},
],
],
},
}}
/>
function rewriteLinkNodes(node: Node) {
if (node.type === "element" && node.tagName === "a" && node.properties.href) {
const href = decodeURIComponent(node.properties.href)
//마크다운 확장자로 위키링크인지 아닌지 구분
const internalLink = href.endsWith(".md");
if (internalLink) {
node.properties.className = "internal-link";
node.properties.href = path.basename(href, ".md");
}
}
return node;
}
이런 방법에도 불구하고 remark-wiki-link를 사용한 이유는
- 옵시디언에서 문서를 작성할 때 위키링크로 작성하는게 기본 설정으로 되어있고
- 위키링크로 작성한 파일을 굳이 또 버튼 몇 번 더 눌러서 경로로 변경을 하는게 상당히 귀찮았기 때문
- 코드가 지저분해지는 것이 너무너무 싫었어요 ...
잘 만든 바퀴가 있으니 얼마나 다행인가 .... 만약에 근데 저 플러그인을 못찾았다면 바로 주저없이 rehype-rewrite로 노드를 바꾸는 방법을 선택할것이다. .... 하여튼 이번에 이런저런 사소한 설정이 생각보다 안?부드?럽게 넘어가서 조금씩 만져주고 있는데, 이 wiki link만큼 나를 힘들게 한 녀석은 없었다.......그래도 나름 재밌었어요 굿~👍